2-5 文件流下载got:VS扩展批量下载功能开发
为什么选择 got 而非 axios
在 Node.js 中发起 HTTP 请求,大多数人第一时间想到 axios。但在处理流式文件下载时,got 库表现更优:连接稳定性更高、请求成功率更好,且原生支持 ESM(ES Module)语法。
got 的周下载量非常大,官方使用原生 ESM 编写所有代码。因此在使用 got 时,项目文件需使用 .mjs 后缀(或在 package.json 中设置 "type": "module"),以告知 Node.js 采用 ESM 语法导入模块。
开发流程
1. 获取扩展列表并解析数据
使用 child_process 模块执行 code --list-extensions --show-versions 命令:
import { exec } from 'child_process'
import { promisify } from 'util'
const execCmd = promisify(exec)
async function main() {
const { stdout } = await execCmd('code --list-extensions --show-versions')
const extensions = stdout
.split('\n')
.filter(line => line.trim())
.map(item => getExtensionInfo(item))
}
javascript
2. 正则解析扩展信息
将 creator.name@version 格式的字符串解析为结构化对象:
function getExtensionInfo(str) {
const regex = /^([a-zA-Z0-9-]+)\.([a-zA-Z0-9-]+)@(.+)$/
const match = str.match(regex)
return {
creator: match[1],
name: match[2],
version: match[3]
}
}
javascript
3. 创建下载目录
import fs from 'fs'
function mkdir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
}
javascript
4. 流式下载文件
使用 got.stream() 获取流式数据并写入本地文件:
import got from 'got'
import path from 'path'
import fs from 'fs'
function downloadFile({ creator, name, version }, retries = 3) {
const url = `https://${creator}.gallery.vsassets.io/_apis/public/gallery/publisher/${creator}/extension/${name}/${version}/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage`
const fileName = `${creator}.${name}-${version}.vsix`
const filePath = path.join('downloads', fileName)
// 跳过已存在的文件
if (fs.existsSync(filePath)) {
console.log(`文件已存在,跳过: ${fileName}`)
return
}
const downloadStream = got.stream(url, {
retry: {
limit: 5,
methods: ['GET'],
statusCodes: [429] // 仅在 429(请求过于频繁)时重试
}
})
const writeStream = fs.createWriteStream(filePath)
downloadStream.pipe(writeStream)
downloadStream.on('error', (error) => {
console.error(`下载失败: ${fileName}`, error.message)
// 删除失败的空文件
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
}
if (retries > 0) {
downloadFile({ creator, name, version }, retries - 1)
}
})
writeStream.on('finish', () => {
console.log(`文件已下载完成: ${fileName}`)
})
}
javascript
5. 执行批量下载
// 在 main 函数中
mkdir('downloads')
for (let i = 0; i < extensions.length; i++) {
downloadFile(extensions[i])
}
javascript
关键技术点
promisify 转换回调为异步
child_process.exec 默认使用回调函数,通过 promisify 转换为 Promise 形式,便于使用 async/await 语法。如果不转换,回调中的 for 循环会立即执行完毕并退出进程,导致后续下载无法完成。
nodemon 辅助调试
安装 nodemon 作为开发依赖,文件修改后自动重启,避免频繁手动停止和重启:
npm install -D nodemon
npx nodemon index.mjs
bash
重试策略
got 提供两层重试机制:
| 层级 | 位置 | 作用 |
|---|---|---|
| got 内置重试 | retry 配置项 | 针对单次请求的流级别重试 |
| 任务级重试 | retries 参数 | 针对整个下载任务的逻辑级重试 |
两者叠加后,总重试次数为 retries * retry.limit。通过 statusCodes 限定只在 429 状态码时触发流级重试,避免在非限流错误上浪费重试次数。
重试延迟优化
got 默认的重试间隔随重试次数递增,可能等待时间过长。可通过 calculateDelay 回调自定义延迟:
retry: {
limit: 5,
calculateDelay: ({ computedValue }) => computedValue / 10
}
javascript
将计算出的延迟值除以 10,大幅缩短等待时间。
参考资源
- got GitHub - 官方文档
- got Stream API - 流式下载文档
- ky - 浏览器端轻量请求库(同作者)
↑